/*********************************************************
 *
 *  Ports all the way down - patwd
 *  ------------------------------
 *
 *  callbacks.c
 *
 *  Copyright (C) 2012 by Sebastian Roschke (bykhee@gmail.com)
 *
 *  Callback functions for handling a captured packet, i.e.
 *  sending an SYN/ACK after a captured SYN packet.
 *
 *  Distributed under the terms and conditions of GNU LGPL.
 *
 */

#include <libnet.h>

#include "callbacks.h"
#include "debug.h"
#include "alloc-inl.h"

#define ETHERNET_HEADER 14
#define IP_HDR_MIN 20
#define TCP_HDR_MIN 20

extern u8 ports[NUM_PORTS];

void init_callbacks() {

  stats = ck_alloc(sizeof(struct libnet_stats));

  if (linkl == DLT_EN10MB)
    get_lh_size = get_ethh_size;
  if (linkl == DLT_LINUX_SLL)
    get_lh_size = get_sllh_size;
}

void handle_packet(u_char *args, const struct pcap_pkthdr *header, const u_char *packet) {

  // Copy the buffer
  u_char *pkt = ck_alloc(header->caplen);
  u_char *pkt_org = pkt;
  memcpy(pkt, packet, header->caplen);

  s32 lh_size = get_lh_size(pkt, header);
  if (lh_size < 0) {
    log_error((u8*)"Link layer header size couldn't be determined! Skip it.");
    return;
  }

  // Check if we have enough bytes at hand
  if (header->caplen < lh_size) {
    log_error((u8*)"Not enough bytes read to analyse the ethernet header! Skip it.");
    print_dump(pkt_org, header->caplen);
    return;
  }
  // Ethernet header 14 bytes
  //struct sniff_ethernet *eth_hdr = (struct sniff_ethernet*) packet;
  pkt += lh_size;

  // Check if we have enough bytes for the cast
  if (header->caplen < lh_size + IP_HDR_MIN) {
    log_error((u8*)"Not enough bytes read to analyse the ip header! Skip it.");
    print_dump(pkt_org, header->caplen);
    return;
  }

  // IP header size variable, what a pain - at least 20 bytes
  struct sniff_ip *ip_hdr = (struct sniff_ip*) pkt;
  if (IP_HL(ip_hdr)*4 < IP_HDR_MIN) {
    log_error((u8*)"IP header to short! Skip it.");
    print_dump(pkt_org, header->caplen);
    return;
  }
  // Check if we have enough bytes for the cast
  if (header->caplen < lh_size + IP_HL(ip_hdr)*4 + TCP_HDR_MIN) {
    log_error((u8*)"Not enough bytes read to analyse the tcp header! Skip it.");
    print_dump(pkt_org, header->caplen);
    return;
  }
  pkt += IP_HL(ip_hdr)*4;

  // TCP header size variable - at least 20 bytes
  struct sniff_tcp *tcp_hdr = (struct sniff_tcp*) pkt;
  if (TH_OFF(tcp_hdr) < 5 || TH_OFF(tcp_hdr) > 15) {
    log_error((u8*)"TCP data offset seems bogus!");
    print_dump(pkt_org, header->caplen);
    return;
  }

  u8 *source = ck_alloc(16);
  if (inet_ntop(AF_INET, (const void*)&((struct sniff_ip*)ip_hdr)->ip_src, (char*)source, 16) == NULL) {
    log_error((u8*)"Extracting source from IP header failed!");
    return;
  }
  u8 *dest = ck_alloc(16);
  if (inet_ntop(AF_INET, (const void*)&((struct sniff_ip*)ip_hdr)->ip_dst, (char*)dest, 16) == NULL) {
    log_error((u8*)"Extracting destination from IP header failed!");
    return;
  }

  // Check for the IPs here
  u16 i = 0;
  for (i = 0; i < ips_cnt; i++) {
    if (strncmp((char*)ips[i], (char*)dest, strlen((char*)ips[i])) != 0) return;
  }

  u16 src_port = EXTRACT_16BITS(&tcp_hdr->th_sport);
  u16 dst_port = EXTRACT_16BITS(&tcp_hdr->th_dport);

  if (!ports[dst_port]) {
    return;
  }
  u8* lstr = alloc_printf("SYN request from: %s:%hu to %s:%hu", source, src_port, dest, dst_port);
  log_debug(lstr);
  ck_free(lstr);
  print_dump((void*)pkt, header->caplen);

  ck_free(source);
  ck_free(dest);

  // Do something fancy here, arguably using libnet to send a packet back. :)
  u32 seq_num = ntohl(tcp_hdr->th_seq);
  send_ack(src_port, dst_port, seq_num, ip_hdr->ip_dst, ip_hdr->ip_src);

  ck_free(pkt_org);
}

// builds and sends tcp & ip packet
void send_ack(u16 src_port, u16 dest_port, u32 seq_num, struct in_addr source, struct in_addr dest)
{
  char libnet_errbuf[LIBNET_ERRBUF_SIZE];
	
  libnet_t *context;	// libnet context
  libnet_ptag_t tcp = 0, ipv4 = 0; // libnet protocol blocks

  u8* lstr = NULL;
  // initialize libnet to interface
  context = libnet_init(LIBNET_RAW4, (char*)dev, libnet_errbuf);
  if (context == NULL){
    lstr = alloc_printf("Error opening context: %s\n", libnet_errbuf);
    log_error(lstr);
    ck_free(lstr);
    exit(1);
  }

  // build_tcp_packet(dest_port, src_port, seq_num, ack_num, flags, window, checksum,
  //   urg, hdr_len, payload, p_len, libnet_context, libnet_block)
  tcp = libnet_build_tcp(dest_port, src_port, libnet_get_prand(LIBNET_PRu16),
    (seq_num + 1), TH_SYN|TH_ACK, 4096, 0, 0, LIBNET_TCP_H, NULL, 0, context, tcp);
  if (tcp == -1){
    lstr = alloc_printf("Error building TCP header: %s\n", libnet_errbuf);
    log_error(lstr);
    ck_free(lstr); 
    exit(1);
  }

  //libnet_autobuild_ipv4(uint16_t len, uint8_t prot, uint32_t dst, libnet_t *l);
  ipv4 = libnet_autobuild_ipv4(LIBNET_TCP_H + LIBNET_IPV4_H, IPPROTO_TCP, dest.s_addr, context);
  // build_ip_packet(length, tos, ip_id, flag_offset, ttl, upper_layer_protocol,
  //   checksum, source, dest, payload, p_len, libnet_context, libnet_block)
  //ipv4 = libnet_build_ipv4(LIBNET_TCP_H + LIBNET_IPV4_H, 0, libnet_get_prand(LIBNET_PRu16),
  //  0, 64, IPPROTO_TCP, 0, source.s_addr, dest.s_addr, NULL, 0, context, ipv4);
  if (ipv4 == -1){
    lstr = alloc_printf("Error building IP header: %s\n", libnet_errbuf);
    log_error(lstr);
    ck_free(lstr);
    exit(1);
  }

  // Send packets
  if ((libnet_write(context)) == -1){
    lstr = alloc_printf("Error writing packet: %s\n", libnet_errbuf);
    log_error(lstr);
    ck_free(lstr);
    exit(1);
  }
  libnet_stats(context, CP(stats));
  
  lstr = alloc_printf("Packets sent: %lu Packet errors: %lu Bytes sent: %lu\n",
    CP(stats)->packets_sent, CP(stats)->packet_errors, CP(stats)->bytes_written);
  log_debug(lstr);
  ck_free(lstr);

  // Shutdown libnet
  libnet_destroy(context);	
}
